﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.Diagnostics;

namespace Sencha.Serialization.Serializers
{
	using Contracts;

	public class ObjectSerializer<ObjectT> : TypeSerializer<ObjectT>, ITypeSerializer
	{
		private delegate void WriteDelegate(ObjectSerializer<ObjectT> @this, IJsonWriter writer, ObjectT value, int[] fields);
		private delegate ObjectT ReadDelegate(ObjectSerializer<ObjectT> @this, IJsonReader reader);

		private class AmbiguousTypeSerializer : ITypeSerializer
		{
			public MemberDescription Member1;
			public MemberDescription Member2;
			public List<MemberDescription> OtherMembers;

			public AmbiguousTypeSerializer(MemberDescription m1, MemberDescription m2)
			{
				this.Member1 = m1;
				this.Member2 = m2;
				OtherMembers = new List<MemberDescription>();
			}

			#region ITypeSerializer Members

			public TypeDescription SerializedType
			{
				get { return Member1.DeclaringType; }
			}

			public object Deserialize(IJsonReader reader)
			{
				throw new Exceptions.AmbiguousMemberFound(Member1.DeclaringType, Member1.Name, Member2.DeclaringType, Member2.Name);
			}

			public void Serialize(IJsonWriter writer, object value)
			{
				throw new NotImplementedException();
			}

			#endregion
		}
		private class ByReferenceEqualityComparer : IEqualityComparer<string>
		{
			public static readonly ByReferenceEqualityComparer Default = new ByReferenceEqualityComparer();

			#region IEqualityComparer<string> Members

			public bool Equals(string x, string y)
			{
				return Object.ReferenceEquals(x, y);
			}

			public int GetHashCode(string obj)
			{
				return obj.GetHashCode();
			}

			#endregion
		}

		private static readonly WriteDelegate WriteFields;
		private static readonly ReadDelegate ReadFields;
		private static readonly string[] FieldNames;

		private Type m_objectType;
		private Dictionary<string, ITypeSerializer> m_heirsByMemberName;

		private Dictionary<string, int> m_fieldIdxByName;
		private Dictionary<int, string> m_nameByFieldIdx;
		private int[] m_fieldsOrder;
		private int m_contractVersion;

		public DataContract Contract { get; private set; }

		static ObjectSerializer()
		{
			var allowPrivateMembers = ReflectionExtentions.IsReflectionPermissionGranted();

			var bindingFlags = (allowPrivateMembers ? BindingFlags.NonPublic : 0) | BindingFlags.Public | BindingFlags.Instance;

			var serializerType = typeof(ObjectSerializer<ObjectT>);
			var objectType = serializerType.GetGenericArguments()[0];

			var allFields = objectType.GetFields(bindingFlags).Where(f => f.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Length == 0).ToArray();
			var allProperties = objectType.GetProperties(bindingFlags);

			var readDynamicMethod = new DynamicMethod("ReadFields<" + objectType.Name + ">", objectType, new[] { serializerType, typeof(IJsonReader) }, serializerType.Module, allowPrivateMembers);
			readDynamicMethod.DefineParameter(1, ParameterAttributes.None, "reader");
			readDynamicMethod.DefineParameter(2, ParameterAttributes.None, "context");
			readDynamicMethod.InitLocals = true;

			var writeDynamicMethod = new DynamicMethod("WriteFields<" + objectType.Name + ">", typeof(void), new[] { serializerType, typeof(IJsonWriter), objectType, typeof(int[]) }, serializerType.Module, allowPrivateMembers);
			writeDynamicMethod.DefineParameter(1, ParameterAttributes.None, "writer");
			writeDynamicMethod.DefineParameter(2, ParameterAttributes.None, "value");
			writeDynamicMethod.DefineParameter(3, ParameterAttributes.None, "context");
			writeDynamicMethod.InitLocals = true;

			var getHierarchy = typeof(ISerializationContext).GetProperty("Hierarchy").GetGetMethod(); Debug.Assert(getHierarchy != null, "ISerializationContext::Hierarchy is missing");
			var getFormat = typeof(ISerializationContext).GetProperty("Format").GetGetMethod(); Debug.Assert(getHierarchy != null, "ISerializationContext::Format is missing");
			var stackPush = typeof(System.Collections.Stack).GetMethod("Push", new[] { typeof(object) }); Debug.Assert(stackPush != null, "Stack::Push is missing");
			var stackPop = typeof(System.Collections.Stack).GetMethod("Pop"); Debug.Assert(stackPop != null, "Stack::Pop is missing");

			var throwIfEof = typeof(JsonReaderExtentions).GetMethod("ThrowIfEndOfStream", new[] { typeof(IJsonReader), typeof(JsonToken) });
			var throwReaderStackImba = typeof(JsonReaderExtentions).GetMethod("ThrowStackImbalance", new[] { typeof(IJsonReader) });
			var throwIfMemberNameIsEmpty = typeof(JsonReaderExtentions).GetMethod("ThrowIfMemberNameIsEmpty", new[] { typeof(IJsonReader), typeof(string) });
			var throwWriterStackImba = typeof(JsonWriterExtentions).GetMethod("ThrowStackImbalance", new[] { typeof(IJsonWriter) });

			var allReadMethods = typeof(JsonReaderExtentions).GetMethods();
			var readValue = (Func<Type, MethodInfo>)(t => allReadMethods.Where(m => m.Name.StartsWith("Read") && m.Name != "ReadMember" && m.GetParameters().Length == 2 && m.ReturnType == t)
				.DefaultIfEmpty(allReadMethods.Where(m => m.IsGenericMethod && m.Name == "ReadValue").Single().MakeGenericMethod(t))
				.SingleOrDefault());
			var allWriteMethods = typeof(JsonWriterExtentions).GetMethods();
			var writeValue = (Func<Type, MethodInfo>)(t => allWriteMethods.Where(m => m.Name.StartsWith("Write") && m.Name != "WriteMember" && m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType == t)
				.DefaultIfEmpty(allWriteMethods.Where(m => m.IsGenericMethod && m.Name == "WriteValue").Single().MakeGenericMethod(t))
				.SingleOrDefault());
			var readMemberMethod = allReadMethods.Where(m => m.Name == "ReadMember" && m.GetParameters().Length == 2).Single();			
			var writeMemberMethod = typeof(IJsonWriter).GetMethods().Where(m => m.Name == "Write" && m.GetParameters()[0].ParameterType == typeof(JsonMember)).Single();

			{
				var nextToken = typeof(IJsonReader).GetMethod("NextToken", Type.EmptyTypes); Debug.Assert(nextToken != null, "IJsonReader::NextToken is missing");
				var getToken = typeof(IJsonReader).GetProperty("Token").GetGetMethod(); Debug.Assert(getToken != null, "IJsonReader::Token is missing");
				var getContext = typeof(IJsonReader).GetProperty("Context").GetGetMethod(); Debug.Assert(getContext != null, "IJsonReader::Context is missing");
				var mapMemberNameToFieldIdx = serializerType.GetMethod("MapMemberNameToFieldId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var validateValue = serializerType.GetMethod("ValidateValue", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var getHeirSerializer = serializerType.GetMethod("GetHeirSerializer", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var skipValueOrThrowException = serializerType.GetMethod("SkipValueOrThrowException", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var setFieldValue = serializerType.GetMethod("SetFieldValue", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var createInstance = serializerType.GetMethod("CreateInstance", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var deserialize = typeof(ITypeSerializer).GetMethod("Deserialize", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

				var objectCtr = !objectType.IsAbstract ? objectType.GetConstructor(bindingFlags, null, Type.EmptyTypes, null) : null;

				var il = readDynamicMethod.GetILGenerator();
				var instanceLocal = il.DeclareLocal(objectType);
				var contextLocal = il.DeclareLocal(typeof(System.Collections.Stack));
				var memberNameLocal = il.DeclareLocal(typeof(string));
				var serializerLocal = il.DeclareLocal(typeof(ITypeSerializer));

				var readFieldsJumpTable = new Label[allFields.Length + allProperties.Length];
				var assignFieldsJumpTable = new Label[allFields.Length + allProperties.Length + 1];
				var fieldLocals = new LocalBuilder[allFields.Length + allProperties.Length];
				var fieldIsSetLocals = new LocalBuilder[fieldLocals.Length];
				FieldNames = new string[fieldLocals.Length];
				for (int i = 0; i < allFields.Length; i++)
				{
					fieldLocals[i] = il.DeclareLocal(allFields[i].FieldType);
					fieldIsSetLocals[i] = il.DeclareLocal(typeof(bool));
					FieldNames[i] = allFields[i].Name;
					readFieldsJumpTable[i] = il.DefineLabel();
					assignFieldsJumpTable[i] = il.DefineLabel();
				}
				var offset = allFields.Length;
				for (int i = 0; i < allProperties.Length; i++)
				{
					fieldLocals[offset + i] = il.DeclareLocal(allProperties[i].PropertyType);
					fieldIsSetLocals[offset + i] = il.DeclareLocal(typeof(bool));
					FieldNames[offset + i] = allProperties[i].Name;
					readFieldsJumpTable[offset + i] = il.DefineLabel();
					assignFieldsJumpTable[offset + i] = il.DefineLabel();
				}

				var whileBodyLabel = il.DefineLabel();
				var whileEndLabel = il.DefineLabel();
				var whileConditionLabel = il.DefineLabel();
				var createNewObjectLabel = il.DefineLabel();
				var defaultFieldsLabel = il.DefineLabel();
				var endOfFieldsBlock = il.DefineLabel();
				var endOfAssignBlock = assignFieldsJumpTable[assignFieldsJumpTable.Length - 1] = il.DefineLabel();
				var skipOrThrowLabel = il.DefineLabel();
				var assignValuesLabel = il.DefineLabel();

				il.Emit(OpCodes.Nop);
				//if (!objectType.IsValueType)
				//{
				//    il.Emit(OpCodes.Ldarg_1); // reader
				//    il.Emit(OpCodes.Callvirt, getContext); // reader.Context
				//    il.Emit(OpCodes.Callvirt, getHierarchy); // reader.Context.Hierarchy
				//    il.Emit(OpCodes.Stloc_1);
				//    il.Emit(OpCodes.Ldloc_1);
				//    il.Emit(OpCodes.Ldloc_0);
				//    il.Emit(OpCodes.Callvirt, stackPush); // reader.Context.Hierarchy.Push(dict)
				//}
				il.Emit(OpCodes.Br, whileConditionLabel);
				il.MarkLabel(whileBodyLabel);

				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Ldc_I4_0); // false
				il.Emit(OpCodes.Call, readMemberMethod);
				il.Emit(OpCodes.Stloc_2); // memberName = reader.ReadMember(false);

				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Ldloc_2); // memberName 
				il.Emit(OpCodes.Call, throwIfMemberNameIsEmpty); // ThrowIfMemberNameIsEmpty(reader, memberName);
				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Ldc_I4, (int)JsonToken.EndOfObject);
				il.Emit(OpCodes.Call, throwIfEof); // ThrowIfEndOfStream(reader, JsonToken.EndOfObject);

				il.Emit(OpCodes.Ldarg_0); // load serializer
				il.Emit(OpCodes.Ldloc_2); // memberName
				il.Emit(OpCodes.Call, mapMemberNameToFieldIdx); // serializer.MapMemberNameToFieldIdx(memberName);
				il.Emit(OpCodes.Switch, readFieldsJumpTable); // switch(fieldIdx)
				il.Emit(OpCodes.Br, defaultFieldsLabel);
				for (int i = 0; i < fieldLocals.Length; i++)
				{
					il.MarkLabel(readFieldsJumpTable[i]); // case 0: ...
					il.Emit(OpCodes.Ldarg_1); // load reader 
					il.Emit(OpCodes.Callvirt, nextToken); // reader.NextToken();   
					il.Emit(OpCodes.Pop);
					il.Emit(OpCodes.Ldarg_0); // load serializer
					il.Emit(OpCodes.Ldarg_1); // load reader
					il.Emit(OpCodes.Ldloc_2); // load memberName
					il.Emit(OpCodes.Callvirt, validateValue); // serializer.ValidateValue(reader, memberName);

					il.Emit(OpCodes.Ldarg_1); // load reader
					il.Emit(OpCodes.Ldc_I4_0); // false
					il.Emit(OpCodes.Call, readValue(fieldLocals[i].LocalType));
					il.Emit(OpCodes.Stloc, fieldLocals[i]); // _fieldValue = reader.ReadValue<FieldT>(false);
					il.Emit(OpCodes.Ldc_I4_1); // true
					il.Emit(OpCodes.Stloc, fieldIsSetLocals[i]); // _fieldValue_isSet = true                        
					il.Emit(OpCodes.Br, endOfFieldsBlock);
				}
				il.MarkLabel(defaultFieldsLabel); // default:
				{
					il.Emit(OpCodes.Ldarg_0); // load serializer
					il.Emit(OpCodes.Ldarg_1); // load reader
					il.Emit(OpCodes.Callvirt, getHeirSerializer);
					il.Emit(OpCodes.Castclass, typeof(ITypeSerializer));
					il.Emit(OpCodes.Stloc_3); // otherSerializer = serializer.GetHeirSerializer(reader);
					il.Emit(OpCodes.Ldloc_3); // load otherSerializer
					il.Emit(OpCodes.Ldnull);
					il.Emit(OpCodes.Ceq);
					il.Emit(OpCodes.Brtrue_S, skipOrThrowLabel); // if ( otherSerializer == null ) goto skipOrThrowLabel;
					il.Emit(OpCodes.Ldloc_3); // load otherSerializer
					il.Emit(OpCodes.Ldarg_1); // load reader
					il.Emit(OpCodes.Callvirt, deserialize);
					if (objectType.IsValueType)
						il.Emit(OpCodes.Unbox_Any, objectType);
					else
						il.Emit(OpCodes.Castclass, objectType);
					il.Emit(OpCodes.Stloc_0); // instance = (ObjectT)otherSerializer.Deserialize(reader);
					il.Emit(OpCodes.Br, assignValuesLabel);
					il.MarkLabel(skipOrThrowLabel);
					il.Emit(OpCodes.Ldarg_1); // load reader 
					il.Emit(OpCodes.Callvirt, nextToken); // reader.NextToken();   
					il.Emit(OpCodes.Pop);
					il.Emit(OpCodes.Ldarg_0); // load serializer
					il.Emit(OpCodes.Ldarg_1); // load reader
					il.Emit(OpCodes.Callvirt, skipValueOrThrowException); // serializer.SkipValueOrThrowException(reader);                    
				}
				il.MarkLabel(endOfFieldsBlock);
				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Ldc_I4, (int)JsonToken.EndOfObject);
				il.Emit(OpCodes.Call, throwIfEof); // ThrowIfEndOfStream(reader, JsonToken.EndOfObject);
				// while codition
				il.MarkLabel(whileConditionLabel);
				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Callvirt, getToken); // reader.Token;
				il.Emit(OpCodes.Ldc_I4, (int)JsonToken.Member);
				il.Emit(OpCodes.Ceq);
				il.Emit(OpCodes.Brtrue, whileBodyLabel);
				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Callvirt, nextToken); // reader.NextToken();
				il.Emit(OpCodes.Brfalse_S, whileEndLabel);
				il.Emit(OpCodes.Ldarg_1); // load reader
				il.Emit(OpCodes.Callvirt, getToken); // reader.Token;
				il.Emit(OpCodes.Ldc_I4, (int)JsonToken.EndOfObject);
				il.Emit(OpCodes.Ceq);
				il.Emit(OpCodes.Brfalse, whileBodyLabel);
				il.MarkLabel(whileEndLabel);
				//if (!objectType.IsValueType)
				//{
				//    il.Emit(OpCodes.Ldloc_1); // load stack
				//    il.Emit(OpCodes.Callvirt, stackPop);
				//    il.Emit(OpCodes.Ldloc_0); // load dict
				//    il.Emit(OpCodes.Ceq); // stack.Pop() == dict
				//    il.Emit(OpCodes.Brtrue_S, createNewObjectLabel);
				//    il.Emit(OpCodes.Ldarg_1); // load reader
				//    il.Emit(OpCodes.Call, throwReaderStackImba);
				//}
				il.MarkLabel(createNewObjectLabel);
				if (objectType.IsValueType)
				{
					il.Emit(OpCodes.Ldloca_S, instanceLocal);
					il.Emit(OpCodes.Initobj, objectType); // instance = new ObjectT();
				}
				else if (objectCtr != null)
				{
					il.Emit(OpCodes.Newobj, objectCtr);
					il.Emit(OpCodes.Stloc_0); // instance = new ObjectT();
				}
				else
				{
					il.Emit(OpCodes.Ldarg_0); // load serializer
					il.Emit(OpCodes.Callvirt, createInstance);
					il.Emit(OpCodes.Stloc_0); // instance = serializer.CreateInstance();
				}
				il.MarkLabel(assignValuesLabel);
				for (int i = 0; i < fieldLocals.Length; i++)
				{
					il.MarkLabel(assignFieldsJumpTable[i]);
					il.Emit(OpCodes.Ldloc, fieldIsSetLocals[i]);
					il.Emit(OpCodes.Brfalse_S, assignFieldsJumpTable[i + 1]); // if (!_fieldValue_isSet) goto next;

					if (i < allFields.Length)
					{
						il.Emit(objectType.IsValueType ? OpCodes.Ldloca_S : OpCodes.Ldloc, instanceLocal);
						il.Emit(OpCodes.Ldloc, fieldLocals[i]);
						il.Emit(OpCodes.Stfld, allFields[i]); // instance.field = _fieldValue;
					}
					else
					{
						var setMethod = allProperties[i - allFields.Length].GetSetMethod(allowPrivateMembers);

						if (setMethod != null)
						{
							il.Emit(objectType.IsValueType ? OpCodes.Ldloca_S : OpCodes.Ldloc, instanceLocal);
							il.Emit(OpCodes.Ldloc, fieldLocals[i]);
							if (setMethod.IsVirtual && !setMethod.IsFinal)
								il.Emit(OpCodes.Callvirt, setMethod);  // instance.set_Field(_fieldValue);
							else
								il.Emit(OpCodes.Call, setMethod); // instance.set_Field(_fieldValue);
						}
						else
						{
							il.Emit(OpCodes.Ldarg_0); // load serializer
							il.Emit(OpCodes.Ldarg_1); // load reader
							il.Emit(OpCodes.Ldloca_S, instanceLocal);
							il.Emit(OpCodes.Ldstr, FieldNames[i]); // load "fieldName"
							il.Emit(OpCodes.Ldloc, fieldLocals[i]); // load _fieldValue
							il.Emit(OpCodes.Callvirt, setFieldValue.MakeGenericMethod(fieldLocals[i].LocalType)); // serializer.SetFieldValue<string>(reader, ref instance, "CachePath", cachePath);
						}
					}
				}
				il.MarkLabel(endOfAssignBlock);
				il.Emit(OpCodes.Ldloc_0);
				il.Emit(OpCodes.Ret); // return instance
			}

			{
				var getContext = typeof(IJsonWriter).GetProperty("Context").GetGetMethod(); Debug.Assert(getContext != null, "IJsonWriter::Context is missing");
				var mapFieldIdToMemberName = serializerType.GetMethod("MapFieldIdToMemberName", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var getFieldValue = serializerType.GetMethod("GetFieldValue", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
				var jsonMemberCtr = typeof(JsonMember).GetConstructor(new Type[] { typeof(string), typeof(bool) });

				var il = writeDynamicMethod.GetILGenerator();
				var idxLocal = il.DeclareLocal(typeof(int));
				var fieldsLoc = il.DeclareLocal(typeof(int[]));				

				var writeFieldsJumpTable = new Label[allFields.Length + allProperties.Length];
				for (int i = 0; i < writeFieldsJumpTable.Length; i++) writeFieldsJumpTable[i] = il.DefineLabel();

				var whileBodyLabel = il.DefineLabel();
				var endOfFieldsBlock = il.DefineLabel();
				var whileConditionLabel = il.DefineLabel();
				var returnLabel = il.DefineLabel();

				il.Emit(OpCodes.Nop);
				il.Emit(OpCodes.Ldarg_3);
				il.Emit(OpCodes.Stloc_1); // (local)fields = (argument)fields;
				//if (!objectType.IsValueType)
				//{
				//    il.Emit(OpCodes.Ldarg_1); // writer
				//    il.Emit(OpCodes.Callvirt, getContext); // writer.Context
				//    il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
				//    il.Emit(OpCodes.Ldarg_2); // value
				//    il.Emit(OpCodes.Callvirt, stackPush); // writer.Context.Hierarchy.Push(value)
				//}                   
				il.Emit(OpCodes.Ldc_I4_0);
				il.Emit(OpCodes.Stloc_0); // i = 0;
				il.Emit(OpCodes.Br, whileConditionLabel);
				il.MarkLabel(whileBodyLabel);
				il.Emit(OpCodes.Ldloc_1); // fields
				il.Emit(OpCodes.Ldloc_0);
				il.Emit(OpCodes.Ldelem_I4); // fields[i]
				il.Emit(OpCodes.Switch, writeFieldsJumpTable); // switch(fields[i])
				il.Emit(OpCodes.Br, endOfFieldsBlock);
				// case i: ...
				for (int i = 0; i < writeFieldsJumpTable.Length; i++)
				{
					var fieldType = typeof(void);
					il.MarkLabel(writeFieldsJumpTable[i]);

					il.Emit(OpCodes.Ldarg_1); // writer

					il.Emit(OpCodes.Ldarg_0); // serializer
					il.Emit(OpCodes.Ldc_I4, i); // i
					il.Emit(OpCodes.Call, mapFieldIdToMemberName); // serializer.MapFieldIdToMemberName(i)
					il.Emit(OpCodes.Ldc_I4_1); // escapedAndQuoted : true
					il.Emit(OpCodes.Newobj, jsonMemberCtr);
					il.Emit(OpCodes.Callvirt, writeMemberMethod); // writer.Write((JsonMember)serializer.MapFieldIdToMemberName(i))

					il.Emit(OpCodes.Ldarg_1); // writer
					//  value
					if (i < allFields.Length)
					{
						fieldType = allFields[i].FieldType;
						il.Emit(objectType.IsValueType ? OpCodes.Ldarga_S : OpCodes.Ldarg_S, (short)2); // value
						il.Emit(OpCodes.Ldfld, allFields[i]); // value.Field
					}
					else
					{
						var prop = allProperties[i - allFields.Length];
						var getMethod = prop.GetGetMethod(allowPrivateMembers);
						fieldType = prop.PropertyType;

						if (getMethod != null)
						{
							il.Emit(objectType.IsValueType ? OpCodes.Ldarga_S : OpCodes.Ldarg_S, (short)2); // value                                
							if (getMethod.IsVirtual && !getMethod.IsFinal)
								il.Emit(OpCodes.Callvirt, getMethod);  // value.get_Field();
							else
								il.Emit(OpCodes.Call, getMethod); // value.get_Field();
						}
						else
						{
							il.Emit(OpCodes.Ldarg_0); // load serializer
							il.Emit(OpCodes.Ldarg_1); // load writer
							il.Emit(OpCodes.Ldarga_S, 2); // load value-ref
							il.Emit(OpCodes.Ldstr, FieldNames[i]); // load "fieldName"                                
							il.Emit(OpCodes.Callvirt, getFieldValue.MakeGenericMethod(fieldType)); // serializer.GetFieldValue<string>(writer, ref instance, "FieldName");
						}
					}
					il.Emit(OpCodes.Call, writeValue(fieldType)); // writer.WriteValue<ValueT>(value);
					il.Emit(OpCodes.Br, endOfFieldsBlock);
				}
				il.MarkLabel(endOfFieldsBlock);

				il.Emit(OpCodes.Ldloc_0);
				il.Emit(OpCodes.Ldc_I4_1);
				il.Emit(OpCodes.Add);
				il.Emit(OpCodes.Stloc_0); // i++

				il.MarkLabel(whileConditionLabel);
				il.Emit(OpCodes.Ldloc_0); // i
				il.Emit(OpCodes.Ldloc_1); // fields
				il.Emit(OpCodes.Ldlen); // field.Length
				il.Emit(OpCodes.Conv_I4);
				il.Emit(OpCodes.Clt);
				il.Emit(OpCodes.Brtrue, whileBodyLabel);
				//if (!objectType.IsValueType)
				//{
				//    il.Emit(OpCodes.Ldarg_0); // writer
				//    il.Emit(OpCodes.Callvirt, getContext); // writer.Context
				//    il.Emit(OpCodes.Callvirt, getHierarchy); // writer.Context.Hierarchy						
				//    il.Emit(OpCodes.Callvirt, stackPop); // writer.Context.Hierarchy.Pop()
				//    il.Emit(OpCodes.Ldarg_1); // array
				//    il.Emit(OpCodes.Ceq); // array
				//    il.Emit(OpCodes.Brtrue_S, returnLabel);
				//    il.Emit(OpCodes.Ldarg_0); // load writer
				//    il.Emit(OpCodes.Call, throwWriterStackImba);
				//}
				il.MarkLabel(returnLabel);
				il.Emit(OpCodes.Ret);
			}

			ReadFields = (ReadDelegate)readDynamicMethod.CreateDelegate(typeof(ReadDelegate));
			WriteFields = (WriteDelegate)writeDynamicMethod.CreateDelegate(typeof(WriteDelegate));
		}
		public ObjectSerializer(DataContract contract, TypeDescription typeDescription)
		{
			if (contract == null)
				throw new ArgumentNullException("contract");
			if (typeDescription == null)
				throw new ArgumentNullException("objectType");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			this.TypeDescription = typeDescription;
			this.Contract = contract;

			m_objectType = typeDescription.GetClrType();

		}

		public override ObjectT Deserialize(IJsonReader reader)
		{
			if (reader == null)
				throw new ArgumentNullException("reader");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (reader.Token == JsonToken.Null)
				return default(ObjectT);

			this.InitLookups();
			return ReadFields(this, reader);
		}
		public override void Serialize(IJsonWriter writer, ObjectT value)
		{
			if (writer == null)
				throw new ArgumentNullException("writer");
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			if (value == null)
			{
				writer.WriteNull();
				return;
			}
			this.InitLookups();

			// object
			writer.Write(JsonToken.BeginObject);
			WriteFields(this, writer, value, m_fieldsOrder);
			writer.Write(JsonToken.EndOfObject);
		}

		private void InitLookups()
		{
			if (m_fieldIdxByName != null)
				return;

			var members = this.TypeDescription.Members;
			var fieldsNames = FieldNames;
			var fieldOrder = new List<int>(members.Count);
			m_fieldIdxByName = new Dictionary<string, int>(members.Count);
			m_nameByFieldIdx = new Dictionary<int, string>(members.Count);

			for (int i = 0; i < members.Count; i++)
			{
				var memberName = members[i].Name;
                var fieldName = members[i].FieldName;
                var fieldIdx = Array.IndexOf(fieldsNames, fieldName);
				if (fieldIdx == -1)
					continue;
				m_nameByFieldIdx[fieldIdx] = members[i].NameAsJson;
				m_fieldIdxByName[memberName] = fieldIdx;
				fieldOrder.Add(fieldIdx);
			}

			m_fieldsOrder = fieldOrder.ToArray();
		}
		internal int MapMemberNameToFieldId(string memberName)
		{
			var id = default(int);
			if (m_fieldIdxByName.TryGetValue(memberName, out id))
				return id;
			else
				return -1;
		}
		internal string MapFieldIdToMemberName(int fieldIdx)
		{
			var memberName = default(string);
			if (!m_nameByFieldIdx.TryGetValue(fieldIdx, out memberName))
				return null;

			return memberName;
		}
		internal virtual ITypeSerializer GetHeirSerializer(IJsonReader reader)
		{
			var serializer = default(ITypeSerializer);

			if (m_heirsByMemberName == null || m_contractVersion != this.Contract.Version)
			{
				var heirs = new List<TypeDescription>(30);				
				var heirsByMemberName = new Dictionary<string, ITypeSerializer>(120);

			retry:
				var beginingVersion = this.Contract.Version;
				heirs.AddRange(this.Contract.GetTypeHeirs(this.TypeDescription));

				// enumerate every field in heir and add it to member/heir map
				for (var i = 0; i < heirs.Count; i++)
				{
					var heir = heirs[i];
					if (heir.IsInterface && !this.TypeDescription.IsInterface)
						continue;

					foreach (var typeMember in heir.Members)
					{
						if (m_heirsByMemberName.TryGetValue(typeMember.Name, out serializer))
						{
							if (serializer is AmbiguousTypeSerializer)
								(serializer as AmbiguousTypeSerializer).OtherMembers.Add(typeMember);
							else
								m_heirsByMemberName[typeMember.Name] = new AmbiguousTypeSerializer(serializer.SerializedType.Members.Find(typeMember.Name), typeMember);
						}
						else
						{
							m_heirsByMemberName[typeMember.Name] = this.Contract.GetSerializerOfType(typeMember.DeclaringType);
						}
					}

					heirs.AddRange(this.Contract.GetTypeHeirs(heir));
				}

				// check for contract changes during map creation
				var endingVersion = this.Contract.Version;
				if (endingVersion != beginingVersion) 
				{
					heirsByMemberName.Clear();
					heirs.Clear();
					goto retry; // if contract was changed, retry 
				}
				else
				{
					m_contractVersion = endingVersion;
					m_heirsByMemberName = heirsByMemberName;
				}
			}

			var memberName = reader.ReadMember(false);
			if (m_heirsByMemberName.Count == 0 || !m_heirsByMemberName.TryGetValue(memberName, out serializer))
				return null;

			return serializer;
		}

		//
		protected internal virtual void SkipValueOrThrowException(IJsonReader reader)
		{
			if ((reader.Context.Features & Features.SkipUnknownMember) != 0)
				reader.ReadValue<object>(false);
			else
				throw new Exceptions.MemberIsNotFound(reader.ReadMember(false), m_objectType, reader);
		}
		protected internal virtual void ValidateValue(IJsonReader reader, string memberName)
		{

		}
		protected internal virtual void SetFieldValue<T>(IJsonReader reader, ref ObjectT instance, string fieldName, T value)
		{
			if ((reader.Context.Features & Features.SkipUnaccessibleMembers) != 0)
				return;
			else
				throw new Exceptions.TypeContractViolation(this.GetType(), this.TypeDescription, string.Format("have a writable '{0}' property", fieldName));
		}
		protected internal virtual T GetFieldValue<T>(IJsonWriter writer, ref ObjectT instance, string fieldName)
		{
			if ((writer.Context.Features & Features.SkipUnaccessibleMembers) != 0)
				return default(T);
			else
				throw new Exceptions.TypeContractViolation(this.GetType(), this.TypeDescription, string.Format("have a readable '{0}' property", fieldName));
		}
		protected internal virtual ObjectT CreateInstance()
		{
			throw new Exceptions.NoConstructorDefined(this.GetType(), m_objectType);
		}
		//        

		public override string ToString()
		{
			return string.Format("object, {0}", m_objectType);
		}
	}
}
